package lapsnake.model.data;

import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.lang.reflect.Array;

import lapsnake.model.LapNode;

public interface ChildField<T extends LapNode> extends Iterable<T>
{	
	/**Returns the node upon which this is a field.*/
	public LapNode getOwner();
	/**Whether having zero children is an acceptable state.*/
	public boolean isOptional();
	/**Whether having more than one element is an acceptable state.*/
	public boolean isList();
	/**The type of the child field.*/
	public Class<T> getType();
	
	/**The current number of children in this field*/
	public int size();
	/**Returns whether the size is 0.*/
	public default boolean isEmpty()
	{
		return size()==0;
	}
	/**Returns the index of the indicated node, or -1 if it does not exist.*/
	public int indexOf(LapNode n);
	/**Returns the first child in this field*/
	public default T get()
	{
		return get(0);
	}
	/**Returns the child with the given index in this field*/
	public T get(int index);
	/**Returns all the children in this field*/
	public default T[] toArray()
	{
		@SuppressWarnings("unchecked")
		T[] r = (T[]) Array.newInstance(getType(), size());
		for(int i = 0; i<size(); i++)
		{
			r[i] = get(i);
		}
		return r;
	}
	public default T cast(LapNode child)
	{
		return getType().cast(child);
	}
	
	/**Adds a new child to the list.
	 * @throws IllegalArgumentException*/
	public default void add(T child, Object exclude, boolean fireEvents) throws IllegalArgumentException, PropertyVetoException
	{
		set(size(), child, exclude, fireEvents);
	}
	public default void set(T child, Object exclude, boolean fireEvents) throws PropertyVetoException
	{
		set(0, child, exclude, fireEvents);
	}
	
	/**Replaces the child with the given index in the list. If index == size() then this has the same effect as calling add().
	 * @throws IllegalArgumentException If the index is out of range, or if the index is greater than 0 and isList() returns false.*/
	public void set(int index, T child, Object exclude, boolean fireEvents) throws IllegalArgumentException, PropertyVetoException;
	/**Inserts the child at the given index in the list. If index == size() then this has the same effect as calling add().
	 * @throws IllegalArgumentException If the index is out of range, or if isList() returns false and this call would result in there being more than one child.*/
	public default void insert(int index, T child, Object exclude, boolean fireEvents) throws IllegalArgumentException, PropertyVetoException
	{
		set(size(), child, exclude, false);
		for(int i = size()-1; i > index; i--)
		{
			set(i, get(i-1), exclude, false);
		}
		set(index, child, exclude, fireEvents);
	}
	/**Removes the indicated child.
	 * @throws IllegalArgumentException If isOptional() returns false and there is only one child when this is called, or if the index is out of bounds.*/
	public default void remove(int index, Object exclude, boolean fireEvents) throws IllegalArgumentException, PropertyVetoException
	{
		if(index < 0 || index >= size())
			throw new IllegalArgumentException("Index out of range: "+index+"/"+size());
		for(int i = index; i<size()-1; i++)
			set(i, get(i+1), exclude, false);
		remove(exclude, fireEvents);
	}
	/**Removes the indicated child.
	 * @throws IllegalArgumentException If isOptional() returns false and there is only one child when this is called, or if the indicated child does not exist.*/
	public default void remove(LapNode child, Object exclude, boolean fireEvents) throws IllegalArgumentException, PropertyVetoException
	{
		for(int i = 0; i<size(); i++)
		{
			if(get(i) == child)
			{
				remove(i, exclude, fireEvents);
				return;
			}
		}
		throw new IllegalArgumentException(child+" was not in "+this);
	}
	/**Removes the last element in the list.
	 * @throws IllegalArgumentException If there were no elements left when this was called, or if there was only one element and isOptional() returns false.*/
	public void remove(Object exclude, boolean fireEvents) throws PropertyVetoException;
	

    /**Listen to changes to this property. It is illegal to add a listener twice. It is illegal to add null as a listener.
     * This will not prevent the listener from being garbage collected. It will be automatically removed if this happens.*/
	public void addListener(ChildListener<? super T> l);
    /**Remove the indicated listener. Returns false if it has not been added yet. 
     * Note that listeners will be removed automatically if they are garbage collected.*/
	public boolean removeListener(ChildListener<? super T> l);
	/**Returns whether children in this field should be indented by an additional level following a newline.*/
	public boolean isIndented();

	/**Receive events that allow throwing a veto exception when the value is going to change. 
	 * It is illegal to add a listener twice using this method. It is illegal to add null as a listener.
	 * This will not prevent the listener from being garbage collected. It will be automatically removed if this happens.*/
	public void addVetoListener(VetoableChangeListener l);
	
	/**Remove the indicated listener. Returns false if it has not been added yet. 
	 * Note that listeners will be removed automatically if they are garbage collected.*/
	public boolean removeVetoListener(VetoableChangeListener l);
	
	/**Returns whether the NewLine annotation has been registered. This is used while building the compact UI.*/
	public boolean hasNLAnnotation();
	/**Register the NewLine annotation when it is found. This is used while building the compact UI.*/
	public void setNLAnnotation();
}
